home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-04 / bipl.zip / PROGS.ZIP / MTF3.ICN < prev    next >
Text File  |  1993-01-27  |  17KB  |  533 lines

  1. #############################################################################
  2. #
  3. #    File:     mtf3.icn
  4. #
  5. #    Subject:  Program to map tar file
  6. #
  7. #    Author:   Richard Goerwitz
  8. #
  9. #    Date:     June 3, 1991
  10. #
  11. ############################################################################
  12. #
  13. #    Version:  3.4
  14. #
  15. ###########################################################################
  16. #
  17. #  PURPOSE: Maps 15+ char. filenames in a tar archive to 14 chars.
  18. #  Handles both header blocks and the archive itself.  Mtf is intended
  19. #  to facilitate installation of tar'd archives on systems subject to
  20. #  the System V 14-character filename limit.
  21. #
  22. #  USAGE:  mtf inputfile [-r reportfile] [-e .extensions] [-x exceptions]
  23. #
  24. #  "Inputfile" is a tar archive.  "Reportfile" is file containing a
  25. #  list of files already mapped by mtf in a previous run (used to
  26. #  avoid clashes with filenames in use outside the current archive).
  27. #  The -e switch precedes a list of filename .extensions which mtf is
  28. #  supposed to leave unscathed by the mapping process
  29. #  (single-character extensions such as .c and .o are automatically
  30. #  preserved; -e allows the user to specify additional extensions,
  31. #  such as .pxl, .cpi, and .icn).  The final switch, -x, precedes a
  32. #  list of strings which should not be mapped at all.  Use this switch
  33. #  if, say, you have a C file with a structure.field combination such
  34. #  as "thisisveryverybig.hashptr" in an archive that contains a file
  35. #  called "thisisveryverybig.h," and you want to avoid mapping that
  36. #  portion of the struct name which matches the name of the overlong
  37. #  file (to wit, "mtf inputfile -x thisisveryverybig.hashptr").  To
  38. #  prevent mapping of any string (including overlong filenames) begin-
  39. #  ning, say, with "thisisvery," use "mtf inputfile -x thisisvery."
  40. #  Be careful with this option, or you might end up defeating the
  41. #  whole point of using mtf in the first place.
  42. #
  43. #  OUTPUT FORMAT:  Mtf writes a mapped tar archive to the stdout.
  44. #  When finished, it leaves a file called "map.report" in the current
  45. #  directory which records what filenames were mapped and how.  Rename
  46. #  and save this file, and use it as the "reportfile" argument to any
  47. #  subsequent runs of mtf in this same directory.  Even if you don't
  48. #  plan to run mtf again, this file should still be examined, just to
  49. #  be sure that the new filenames are acceptable, and to see if
  50. #  perhaps additional .extensions and/or exceptions should be
  51. #  specified.
  52. #
  53. #  BUGS:  Mtf only maps filenames found in the main tar headers.
  54. #  Because of this, mtf cannot accept nested tar archives.  If you try
  55. #  to map a tar archive within a tar file, mtf will abort with a nasty
  56. #  message about screwing up your files.  Please note that, unless you
  57. #  give mtf a "reportfile" to consider, it knows nothing about files
  58. #  existing outside the archive.  Hence, if an input archive refers to
  59. #  an overlong filename in another archive, mtf naturally will not
  60. #  know to shorten it.  Mtf will, in fact, have no way of knowing that
  61. #  it is a filename, and not, say, an identifier in a C program.
  62. #  Final word of caution:  Try not to use mtf on binaries.  It cannot
  63. #  possibly preserve the correct format and alignment of strings in an
  64. #  executable.  Same goes for compressed files.  Mtf can't map
  65. #  filenames that it can't read!
  66. #
  67. ####################################################################
  68.  
  69.  
  70. global filenametbl, chunkset, short_chunkset   # see procedure mappiece(s)
  71. global extensions, no_nos                      # ditto
  72.  
  73. record hblock(name,junk,size,mtime,chksum,     # tar header struct;
  74.               linkflag,linkname,therest)       # see readtarhdr(s)
  75.  
  76.  
  77. procedure main(a)
  78.     local usage, intext, i, current_list
  79.  
  80.     usage := "usage:  mtf inputfile [-r reportfile] " ||
  81.          "[-e .extensions] [-x exceptions]"
  82.  
  83.     *a = 0 & stop(usage)
  84.  
  85.     intext := open_input_file(a[1]) & pop(a)
  86.  
  87.     i := 0
  88.     extensions := []; no_nos := []
  89.     while (i +:= 1) <= *a do {
  90.     case a[i] of {
  91.         "-r"    :    readin_old_map_report(a[i+:=1])
  92.         "-e"    :    current_list := extensions
  93.         "-x"    :    current_list := no_nos
  94.         default :    put(current_list,a[i])
  95.     }
  96.     }
  97.  
  98.     every !extensions ?:= (=".", tab(0))
  99.     
  100.     # Run through all the headers in the input file, filling
  101.     # (global) filenametbl with the names of overlong files;
  102.     # make_table_of_filenames fails if there are no such files.
  103.     make_table_of_filenames(intext) | {
  104.     write(&errout,"mtf:  no overlong path names to map") 
  105.     a[1] ? (tab(find(".tar")+4), pos(0)) |
  106.       write(&errout,"(Is ",a[1]," even a tar archive?)")
  107.      exit(1)
  108.     } 
  109.  
  110.     # Now that a table of overlong filenames exists, go back
  111.     # through the text, remapping all occurrences of these names
  112.     # to new, 14-char values; also, reset header checksums, and
  113.     # reformat text into correctly padded 512-byte blocks.  Ter-
  114.     # minate output with 512 nulls.
  115.     seek(intext,1)
  116.     every writes(output_mapped_headers_and_texts(intext))
  117.  
  118.     close(intext)
  119.     write_report()   # Record mapped file and dir names for future ref.
  120.     exit(0)
  121.     
  122. end
  123.  
  124.  
  125.  
  126. procedure open_input_file(s)
  127.     local intext
  128.  
  129.     intext := open("" ~== s,"r") |
  130.     stop("mtf:  can't open ",s)
  131.     find("UNIX",&features) |
  132.     stop("mtf:  I'm not tested on non-UNIX systems.")
  133.     s[-2:0] == ".Z" &
  134.         stop("mtf:  sorry, can't accept compressed files")
  135.     return intext
  136. end
  137.  
  138.  
  139.  
  140. procedure readin_old_map_report(s)
  141.     local mapfile, line, chunk, lchunk
  142.  
  143.     initial {
  144.     filenametbl := table()
  145.     chunkset := set()
  146.     short_chunkset := set()
  147.     }
  148.  
  149.     mapfile := open_input_file(s)
  150.     while line := read(mapfile) do {
  151.     line ? {    
  152.         if chunk := tab(many(~' \t')) & tab(upto(~' \t')) &
  153.         lchunk := move(14) & pos(0) then {
  154.         filenametbl[chunk] := lchunk
  155.         insert(chunkset,chunk)
  156.         insert(short_chunkset,chunk[1:16])
  157.         }
  158.     if /chunk | /lchunk
  159.     then stop("mtf:  report file, ",s," seems mangled.")
  160.     }
  161.     }
  162.  
  163. end
  164.  
  165.  
  166.  
  167. procedure make_table_of_filenames(intext)
  168.  
  169.     local header # chunkset is global
  170.  
  171.     # search headers for overlong filenames; for now
  172.     # ignore everything else
  173.     while header := readtarhdr(reads(intext,512)) do {
  174.     # tab upto the next header block
  175.     tab_nxt_hdr(intext,trim_str(header.size),1)
  176.     # record overlong filenames in several global tables, sets
  177.     fixpath(trim_str(header.name))
  178.     }
  179.     *\chunkset ~= 0 | fail
  180.     return &null
  181.  
  182. end
  183.  
  184.  
  185.  
  186. procedure output_mapped_headers_and_texts(intext)
  187.  
  188.     # Remember that filenametbl, chunkset, and short_chunkset
  189.     # (which are used by various procedures below) are global.
  190.     local header, newtext, full_block, block, lastblock
  191.  
  192.     # Read in headers, one at a time.
  193.     while header := readtarhdr(reads(intext,512)) do {
  194.  
  195.     # Replace overlong filenames with shorter ones, according to
  196.     # the conversions specified in the global hash table filenametbl
  197.     # (which were generated by fixpath() on the first pass).
  198.           header.name := left(map_filenams(header.name),100,"\x00")
  199.     header.linkname := left(map_filenams(header.linkname),100,"\x00")
  200.  
  201.     # Use header.size field to determine the size of the subsequent text.
  202.     # Read in the text as one string.  Map overlong filenames found in it
  203.      # to shorter names as specified in the global hash table filenamtbl.
  204.     newtext := map_filenams(tab_nxt_hdr(intext,trim_str(header.size)))
  205.  
  206.     # Now, find the length of newtext, and insert it into the size field.
  207.     header.size := right(exbase10(*newtext,8) || " ",12," ")
  208.  
  209.     # Calculate the checksum of the newly retouched header.
  210.     header.chksum := right(exbase10(get_checksum(header),8)||"\x00 ",8," ")
  211.  
  212.     # Finally, join all the header fields into a new block and write it out
  213.     full_block := ""; every full_block ||:= !header
  214.     suspend left(full_block,512,"\x00")
  215.  
  216.     # Now we're ready to write out the text, padding the final block
  217.     # out to an even 512 bytes if necessary; the next header must start
  218.     # right at the beginning of a 512-byte block.
  219.     newtext ? {
  220.         while block := move(512)
  221.         do suspend block
  222.         pos(0) & next
  223.             lastblock := left(tab(0),512,"\x00")
  224.         suspend lastblock
  225.     }
  226.     }
  227.     # Write out a final null-filled block.  Some tar programs will write
  228.     # out 1024 nulls at the end.  Dunno why.
  229.     return repl("\x00",512)
  230.  
  231. end
  232.  
  233.  
  234.  
  235. procedure trim_str(s)
  236.  
  237.     # Knock out spaces, nulls from those crazy tar header
  238.     # block fields (some of which end in a space and a null,
  239.     # some just a space, and some just a null [anyone know
  240.     # why?]).
  241.     return s ? {
  242.     (tab(many(' ')) | &null) &
  243.         trim(tab(find("\x00")|0))
  244.     }
  245.  
  246. end 
  247.  
  248.  
  249.  
  250. procedure tab_nxt_hdr(f,size_str,firstpass)
  251.  
  252.     # Tab upto the next header block.  Return the bypassed text
  253.     # as a string if not the first pass.
  254.  
  255.     local hs, next_header_offset
  256.  
  257.     hs := integer("8r" || size_str)
  258.     next_header_offset := (hs / 512) * 512
  259.     hs % 512 ~= 0 & next_header_offset +:= 512
  260.     if 0 = next_header_offset then return ""
  261.     else {
  262.     # if this is pass no. 1 don't bother returning a value; we're
  263.     # just collecting long filenames;
  264.     if \firstpass then {
  265.         seek(f,where(f)+next_header_offset)
  266.         return
  267.     }
  268.     else {
  269.         return reads(f,next_header_offset)[1:hs+1] |
  270.         stop("mtf:  error reading in ",
  271.              string(next_header_offset)," bytes.")
  272.     }
  273.     }
  274.  
  275. end
  276.  
  277.  
  278.  
  279. procedure fixpath(s)
  280.     local s2, piece
  281.  
  282.     # Fixpath is a misnomer of sorts, since it is used on
  283.     # the first pass only, and merely examines each filename
  284.     # in a path, using the procedure mappiece to record any
  285.     # overlong ones in the global table filenametbl and in
  286.     # the global sets chunkset and short_chunkset; no fixing
  287.     # is actually done here.
  288.  
  289.     s2 := ""
  290.     s ? {
  291.     while piece := tab(find("/")+1)
  292.     do s2 ||:= mappiece(piece) 
  293.     s2 ||:= mappiece(tab(0))
  294.     }
  295.     return s2
  296.  
  297. end
  298.  
  299.  
  300.  
  301. procedure mappiece(s)
  302.     local chunk, i, lchunk
  303.  
  304.     # Check s (the name of a file or dir as recorded in the tar header
  305.     # being examined) to see if it is over 14 chars long.  If so,
  306.     # generate a unique 14-char version of the name, and store
  307.     # both values in the global hashtable filenametbl.  Also store
  308.     # the original (overlong) file name in chunkset.  Store the
  309.     # first fifteen chars of the original file name in short_chunkset.
  310.     # Sorry about all of the tables and sets.  It actually makes for
  311.     # a reasonably efficient program.  Doing away with both sets,
  312.     # while possible, causes a tenfold drop in execution speed!
  313.     
  314.     # global filenametbl, chunkset, short_chunkset, extensions
  315.     local j, ending
  316.  
  317.     initial {
  318.     /filenametbl := table()
  319.     /chunkset := set()
  320.     /short_chunkset := set()
  321.     }
  322.    
  323.     chunk := trim(s,'/')
  324.     if chunk ? (tab(find(".tar")+4), pos(0)) then {
  325.     write(&errout, "mtf:  Sorry, I can't let you do this.\n",
  326.                    "      You've nested a tar archive within\n",
  327.                    "      another tar archive, which makes it\n",
  328.                    "      likely I'll f your filenames ubar.")
  329.     exit(2)
  330.     }
  331.     if *chunk > 14 then {
  332.     i := 0
  333.  
  334.     if /filenametbl[chunk] then {
  335.     # if we have not seen this file, then...
  336.         repeat {
  337.         # ...find a new unique 14-character name for it;
  338.         # preserve important suffixes like ".Z," ".c," etc.
  339.         # First, check to see if the original filename (chunk)
  340.         # ends in an important extension...
  341.         if chunk ?
  342.             (tab(find(".")),
  343.              ending := move(1) || tab(match(!extensions)|any(&ascii)),
  344.              pos(0)
  345.              )
  346.         # ...If so, then leave the extension alone; mess with the
  347.         # middle part of the filename (e.g. file.with.extension.c ->
  348.         # file.with001.c).
  349.         then {
  350.             j := (15 - *ending - 3)
  351.             lchunk:= chunk[1:j] || right(string(i+:=1),3,"0") || ending
  352.         }
  353.         # If no important extension is present, then reformat the
  354.         # end of the file (e.g. too.long.file.name -> too.long.fi01).
  355.         else lchunk := chunk[1:13] || right(string(i+:=1),2,"0")
  356.  
  357.         # If the resulting shorter file name has already been used...
  358.         if lchunk == !filenametbl
  359.         # ...then go back and find another (i.e. increment i & try
  360.         # again; else break from the repeat loop, and...
  361.         then next else break
  362.         }
  363.             # ...record both the old filename (chunk) and its new,
  364.         # mapped name (lchunk) in filenametbl.  Also record the
  365.         # mapped names in chunkset and short_chunkset.
  366.         filenametbl[chunk] := lchunk
  367.         insert(chunkset,chunk)
  368.         insert(short_chunkset,chunk[1:16])
  369.     }
  370.     }
  371.  
  372.     # If the filename is overlong, return lchunk (the shortened
  373.     # name), else return the original name (chunk).  If the name,
  374.     # as passed to the current function, contained a trailing /
  375.     # (i.e. if s[-1]=="/"), then put the / back.  This could be
  376.     # done more elegantly.
  377.     return (\lchunk | chunk) || ((s[-1] == "/") | "")
  378.  
  379. end
  380.  
  381.  
  382.  
  383. procedure readtarhdr(s)
  384.     local this_block
  385.  
  386.     # Read the silly tar header into a record.  Note that, as was
  387.     # complained about above, some of the fields end in a null, some
  388.     # in a space, and some in a space and a null.  The procedure
  389.     # trim_str() may (and in fact often _is_) used to remove this
  390.     # extra garbage.
  391.  
  392.     this_block := hblock()
  393.     s ? {
  394.     this_block.name     := move(100)    # <- to be looked at later
  395.     this_block.junk     := move(8+8+8)  # skip the permissions, uid, etc.
  396.     this_block.size     := move(12)     # <- to be looked at later
  397.     this_block.mtime    := move(12)
  398.     this_block.chksum   := move(8)      # <- to be looked at later
  399.     this_block.linkflag := move(1)
  400.     this_block.linkname := move(100)    # <- to be looked at later
  401.     this_block.therest  := tab(0)
  402.     }
  403.     integer(this_block.size) | fail  # If it's not an integer, we've hit
  404.                                      # the final (null-filled) block.
  405.     return this_block
  406.  
  407. end
  408.  
  409.  
  410.  
  411. procedure map_filenams(s)
  412.     local el, ch
  413.  
  414.     # Chunkset is global, and contains all the overlong filenames
  415.     # found in the first pass through the input file; here the aim
  416.     # is to map these filenames to the shortened variants as stored
  417.     # in filenametbl (GLOBAL).
  418.  
  419.     local s2, tmp_chunk_tbl, tmp_lst
  420.     static new_chunklist
  421.     initial {
  422.  
  423.         # Make sure filenames are sorted, longest first.  Say we
  424.         # have a file called long_file_name_here.1 and one called
  425.         # long_file_name_here.1a.  We want to check for the longer
  426.         # one first.  Otherwise the portion of the second file which
  427.         # matches the first file will get remapped.
  428.         tmp_chunk_tbl := table()
  429.         every el := !chunkset
  430.         do insert(tmp_chunk_tbl,el,*el)
  431.         tmp_lst := sort(tmp_chunk_tbl,4)
  432.         new_chunklist := list()
  433.         every put(new_chunklist,tmp_lst[*tmp_lst-1 to 1 by -2])
  434.  
  435.     }
  436.  
  437.     s2 := ""
  438.     s ? {
  439.     until pos(0) do {
  440.         # first narrow the possibilities, using short_chunkset
  441.         if member(short_chunkset,&subject[&pos:&pos+15])
  442.             # then try to map from a long to a shorter 14-char filename
  443.         then {
  444.         if match(ch := !new_chunklist) & not match(!no_nos)
  445.         then s2 ||:= filenametbl[=ch]
  446.         else s2 ||:= move(1)
  447.         }
  448.         else s2 ||:= move(1)
  449.     }
  450.     }
  451.     return s2
  452.  
  453. end
  454.  
  455.  
  456. #  From the IPL.  Thanks, Ralph -
  457. #  Author:  Ralph E. Griswold
  458. #  Date:  June 10, 1988
  459. #  exbase10(i,j) convert base-10 integer i to base j
  460. #  The maximum base allowed is 36.
  461.  
  462. procedure exbase10(i,j)
  463.  
  464.    static digits
  465.    local s, d, sign
  466.    initial digits := &digits || &lcase
  467.    if i = 0 then return 0
  468.    if i < 0 then {
  469.       sign := "-"
  470.       i := -i
  471.       }
  472.    else sign := ""
  473.    s := ""
  474.    while i > 0 do {
  475.       d := i % j
  476.       if d > 9 then d := digits[d + 1]
  477.       s := d || s
  478.       i /:= j
  479.       }
  480.    return sign || s
  481.  
  482. end
  483.  
  484. # end IPL material
  485.  
  486.  
  487. procedure get_checksum(r)
  488.     local sum, field
  489.  
  490.     # Calculates the new value of the checksum field for the
  491.     # current header block.  Note that the specification say
  492.     # that, when calculating this value, the chksum field must
  493.     # be blank-filled.
  494.  
  495.     sum := 0
  496.     r.chksum := "        "
  497.     every field := !r
  498.     do every sum +:= ord(!field)
  499.     return sum
  500.  
  501. end
  502.  
  503.  
  504.  
  505. procedure write_report()
  506.  
  507.     # This procedure writes out a list of filenames which were
  508.     # remapped (because they exceeded the SysV 14-char limit),
  509.     # and then notifies the user of the existence of this file.
  510.  
  511.     local outtext, stbl, i, j, mapfile_name
  512.  
  513.     # Get a unique name for the map.report (thereby preventing
  514.     # us from overwriting an older one).
  515.     mapfile_name := "map.report"; j := 1
  516.     until not close(open(mapfile_name,"r"))
  517.     do mapfile_name := (mapfile_name[1:11] || string(j+:=1))
  518.  
  519.     (outtext := open(mapfile_name,"w")) |
  520.     open(mapfile_name := "/tmp/map.report","w") |
  521.          stop("mtf:  Can't find a place to put map.report!")
  522.     stbl := sort(filenametbl,3)
  523.     every i := 1 to *stbl -1 by 2 do {
  524.     match(!no_nos,stbl[i]) |
  525.         write(outtext,left(stbl[i],35," ")," ",stbl[i+1])
  526.     }
  527.     write(&errout,"\nmtf:  ",mapfile_name," contains the list of changes.")
  528.     write(&errout,"      Please save this list!")
  529.     close(outtext)
  530.     return &null
  531.  
  532. end
  533.